import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

public class MatchStockValue extends AbstractMatchStockValue {

	private static MatchStockValue matchStockValue;
	Account accountBuyer = null;
	AbstractOrder orderBuyer = null;
	Account accountSeller = null;
	AbstractOrder orderSeller = null;
	ActiveOrder buyer = null;
	ActiveOrder seller = null;

	Notification notification = null;
	NotificationFormat notificationformat = null;

	private MatchStockValue() {
	}

	public static MatchStockValue getMatchStockValue() {
		if (matchStockValue == null) {
			matchStockValue = new MatchStockValue();
		}
		return matchStockValue;
	}

	public void matchStocks() {
		QueueRepository queueRepository = QueueRepository.getOrderRepository();
		ArrayList<Queue> queues = queueRepository.getAllQueues();
		ArrayList<ActiveOrder> activeBuyOrders;
		ArrayList<ActiveOrder> activeSellOrders;
		ArrayList<ActiveOrder> activeMarketOrders;
		Double maxBuyerPrice = null;
		Double minSellerPrice = null;
		Integer buyerQuantity = null;
		Integer sellerQuantity = null;
		buyer = null;
		seller = null;
		accountBuyer = null;
		orderBuyer = null;
		accountSeller = null;
		orderSeller = null;

		if (queues != null) {
			for (Queue queue : queues) {

				activeBuyOrders = queue.getActiveOrdersFromQueue(queue
						.getBuyQueue());
				activeSellOrders = queue.getActiveOrdersFromQueue(queue
						.getSellQueue());
				activeMarketOrders = queue.getActiveOrdersFromQueue(queue
						.getMarketQueue());

				Collections.sort(activeBuyOrders);
				Collections.sort(activeSellOrders);

				Integer maxBuyerQuantity = null;
				Integer minSellerQuantity = null;

				if (!activeBuyOrders.isEmpty()) {
					maxBuyerPrice = (activeBuyOrders
							.get(activeBuyOrders.size() - 1)).order
							.getPriceLimit();
					maxBuyerQuantity = (((activeBuyOrders.get(activeBuyOrders
							.size() - 1)).order.getQuantity()));

				}

				if (!activeSellOrders.isEmpty()) {
					minSellerPrice = (activeSellOrders.get(0)).order
							.getPriceLimit();
					minSellerQuantity = (((activeSellOrders.get(0)).order
							.getQuantity()));
				}

				if (maxBuyerPrice != null || minSellerPrice != null) {
					if (maxBuyerPrice == minSellerPrice) {
						buyerQuantity = maxBuyerQuantity;
						sellerQuantity = minSellerQuantity;
						buyer = (activeBuyOrders
								.get(activeBuyOrders.size() - 1));
						seller = (activeSellOrders.get(0));
					} else {
						if (!activeMarketOrders.isEmpty()) {
							ActiveOrder marketOrder = activeMarketOrders.get(0);
							if (marketOrder.order.getOrderCategory().equals(
									OrderCategory.BUY)) {
								buyer = marketOrder;
								buyerQuantity = (marketOrder.order
										.getQuantity() - marketOrder.order
										.getFulfilledQuantity());
								sellerQuantity = minSellerQuantity;
								seller = (activeSellOrders.get(0));
							} else if (marketOrder.order.getOrderCategory()
									.equals(OrderCategory.SELL)) {
								seller = marketOrder;
								sellerQuantity = (marketOrder.order
										.getQuantity() - marketOrder.order
										.getFulfilledQuantity());
								buyerQuantity = maxBuyerQuantity;
								buyer = (activeBuyOrders.get(activeBuyOrders
										.size() - 1));
							}
						}
					}
				}

				if (buyer != null) {
					accountBuyer = ((PortfolioRepository
							.getPortfolioRepository())
							.getPortfolioWithID(buyer.order.getPortfolioId()))
							.getAccount(buyer.order.getAccountId());
					orderBuyer = (OrderRepository.getOrderRepository())
							.getOrder(buyer.order.getOrderId());
				}
				if (seller != null) {
					accountSeller = ((PortfolioRepository
							.getPortfolioRepository())
							.getPortfolioWithID(seller.order.getPortfolioId()))
							.getAccount(seller.order.getAccountId());
					orderSeller = (OrderRepository.getOrderRepository())
							.getOrder(seller.order.getOrderId());
				}

				if (buyer != null || seller != null) {
					this.addObservers(accountBuyer);
					this.addObservers(orderBuyer);
					this.addObservers(accountSeller);
					this.addObservers(orderSeller);
					this.addObservers(buyer);
					this.addObservers(seller);
				}

				if (buyer != null && seller != null) {
					if (buyerQuantity == sellerQuantity) {
						if (buyer.order.getFulfilledQuantity() > 0) {
							buyer.order.setFulfilledQuantity(buyer.order
									.getFulfilledQuantity() + buyerQuantity);
						} else {
							buyer.order.setFulfilledQuantity(buyerQuantity);
						}

						if (seller.order.getFulfilledQuantity() > 0) {
							seller.order.setFulfilledQuantity(seller.order
									.getFulfilledQuantity() + sellerQuantity);
						} else {
							seller.order.setFulfilledQuantity(sellerQuantity);
						}
						notifyAllObservers(buyerQuantity);
						if (buyer.order.getOrderType().equals(OrderType.MARKET)) {
							activeMarketOrders.remove(buyer);
						} else {
							activeBuyOrders.remove(buyer);
						}

						if (seller.order.getOrderType()
								.equals(OrderType.MARKET)) {
							activeMarketOrders.remove(seller);
						} else {
							activeSellOrders.remove(seller);
						}

						Stock stock = StockRepository
								.getStockRepository()
								.getStock(
										((StockOrder) buyer.order).getStockId());

						if (buyer.order.getOrderType().equals(OrderType.MARKET)) {
							stock.setStockPrice(seller.order.getPriceLimit());
						} else if (seller.order.getOrderType().equals(
								OrderType.MARKET)) {
							stock.setStockPrice(buyer.order.getPriceLimit());
						} else {
							stock.setStockPrice(buyer.order.getPriceLimit());
						}

						System.out.println("COMPLETE BUY/SELL MATCH!");
						notificationformat = new EmailFormat();
						notificationformat.display();
						notification = new FullfiledOrder(notificationformat);
						notification.orderFullfiledNotification();

					} else if (buyerQuantity < sellerQuantity) {
						Integer buyerQ = buyerQuantity;
						Double stockprice = null;
						sellerQuantity = sellerQuantity - buyerQuantity;
						if (buyer.order.getFulfilledQuantity() > 0) {
							buyer.order.setFulfilledQuantity(buyer.order
									.getFulfilledQuantity() + buyerQuantity);
						} else {
							buyer.order.setFulfilledQuantity(buyerQuantity);
						}

						if (seller.order.getFulfilledQuantity() > 0) {
							seller.order.setFulfilledQuantity(seller.order
									.getFulfilledQuantity() + buyerQuantity);
						} else {
							seller.order.setFulfilledQuantity(buyerQuantity);
						}

						notifyAllObservers(buyerQuantity);
						if (buyer.order.getOrderType().equals(OrderType.MARKET)) {
							activeMarketOrders.remove(buyer);
						} else {
							activeBuyOrders.remove(buyer);
						}

						if (seller.order.getOrderType()
								.equals(OrderType.MARKET)) {

							while ((sellerQuantity > 0)
									&& (!activeBuyOrders.isEmpty())) {
								buyer = (activeBuyOrders.get(activeBuyOrders
										.size() - 1));
								buyerQ = (buyer.order.getQuantity() - buyer.order
										.getFulfilledQuantity());
								if (buyerQ < sellerQuantity) {
									stockprice = (double) buyer.order
											.getPriceLimit();
									sellerQuantity = sellerQuantity - buyerQ;

									if (buyer.order.getFulfilledQuantity() > 0) {
										buyer.order
												.setFulfilledQuantity(buyer.order
														.getFulfilledQuantity()
														+ buyerQ);
									} else {
										buyer.order
												.setFulfilledQuantity(buyerQ);
									}

									if (seller.order.getFulfilledQuantity() > 0) {
										seller.order
												.setFulfilledQuantity(seller.order
														.getFulfilledQuantity()
														+ buyerQ);
									} else {
										seller.order
												.setFulfilledQuantity(buyerQ);
									}

									notifyAllObservers(buyerQ);
									buyer.order.closeOrder(true);
									if (buyer.order.getOrderType().equals(
											OrderType.MARKET)) {
										activeMarketOrders.remove(buyer);
									} else {
										activeBuyOrders.remove(buyer);
									}

								} else if (buyerQ > sellerQuantity) {
									int partialQuantity = buyerQ
											- sellerQuantity;
									stockprice = (double) buyer.order
											.getPriceLimit();

									if (buyer.order.getFulfilledQuantity() > 0) {
										buyer.order
												.setFulfilledQuantity(buyer.order
														.getFulfilledQuantity()
														+ sellerQuantity);
									} else {
										buyer.order
												.setFulfilledQuantity(sellerQuantity);
									}

									if (seller.order.getFulfilledQuantity() > 0) {
										seller.order
												.setFulfilledQuantity(seller.order
														.getFulfilledQuantity()
														+ sellerQuantity);
									} else {
										seller.order
												.setFulfilledQuantity(sellerQuantity);
									}

									notifyAllObservers(sellerQuantity);
									newOrder(buyer.order, partialQuantity);

									Stock stock = StockRepository
											.getStockRepository().getStock(
													((StockOrder) buyer.order)
															.getStockId());
									stock.setStockPrice(buyer.order
											.getPriceLimit());

									if (buyer.order.getOrderType().equals(
											OrderType.MARKET)) {
										activeMarketOrders.remove(buyer);
									} else {
										activeBuyOrders.remove(buyer);
									}

									if (seller.order.getOrderType().equals(
											OrderType.MARKET)) {
										activeMarketOrders.remove(seller);
									} else {
										activeSellOrders.remove(seller);
									}

									break;

								} else if (buyerQ == sellerQuantity) {

									if (buyer.order.getFulfilledQuantity() > 0) {
										buyer.order
												.setFulfilledQuantity(buyer.order
														.getFulfilledQuantity()
														+ buyerQ);
									} else {
										buyer.order
												.setFulfilledQuantity(buyerQ);
									}

									if (seller.order.getFulfilledQuantity() > 0) {
										seller.order
												.setFulfilledQuantity(seller.order
														.getFulfilledQuantity()
														+ buyerQ);
									} else {
										seller.order
												.setFulfilledQuantity(buyerQ);
									}
									notifyAllObservers(buyerQ);
									stockprice = (double) buyer.order
											.getPriceLimit();
									if (buyer.order.getOrderType().equals(
											OrderType.MARKET)) {
										activeMarketOrders.remove(buyer);
									} else {
										activeBuyOrders.remove(buyer);
									}

									if (seller.order.getOrderType().equals(
											OrderType.MARKET)) {
										activeMarketOrders.remove(seller);
									} else {
										activeSellOrders.remove(seller);
									}
									break;
								}
							}

							Stock stock = StockRepository.getStockRepository()
									.getStock(
											((StockOrder) seller.order)
													.getStockId());
							if (stockprice != null) {
								stock.setStockPrice(stockprice);
							}

						} else if (seller.order.getOrderType().equals(
								OrderType.LIMIT)) {

							Stock stock = StockRepository.getStockRepository()
									.getStock(
											((StockOrder) seller.order)
													.getStockId());
							stock.setStockPrice(seller.order.getPriceLimit());
							seller.order.closeOrder(true);
							if (seller.order.getOrderType().equals(
									OrderType.MARKET)) {
								activeMarketOrders.remove(seller);
							} else {
								activeSellOrders.remove(seller);
							}
							newOrder(seller.order, sellerQuantity);
						}

						System.out
								.println("COMPLETE BUY & PARTIAL SELL MATCH!");
						notificationformat = new EmailFormat();
						notificationformat.display();
						notification = new FullfiledOrder(notificationformat);
						System.out.println("Buyer: ");
						notification.orderFullfiledNotification();
						System.out.println("Seller: ");
						notification = new PendingOrder(notificationformat);
						notification.orderPendingNotification();

					} else if (buyerQuantity > sellerQuantity) {
						int sellerQ = sellerQuantity;
						Double stockprice = null;

						if (buyer.order.getFulfilledQuantity() > 0) {
							buyer.order.setFulfilledQuantity(buyer.order
									.getFulfilledQuantity() + sellerQuantity);
						} else {
							buyer.order.setFulfilledQuantity(sellerQuantity);
						}

						if (seller.order.getFulfilledQuantity() > 0) {
							seller.order.setFulfilledQuantity(seller.order
									.getFulfilledQuantity() + sellerQuantity);
						} else {
							seller.order.setFulfilledQuantity(sellerQuantity);
						}

						notifyAllObservers(sellerQuantity);
						seller.order.closeOrder(true);
						if (seller.order.getOrderType()
								.equals(OrderType.MARKET)) {
							activeMarketOrders.remove(seller);
						} else {
							activeSellOrders.remove(seller);
						}
						buyerQuantity = buyerQuantity - sellerQ;

						if (buyer.order.getOrderType().equals(OrderType.MARKET)) {

							while ((buyerQuantity > 0)
									&& (!activeSellOrders.isEmpty())) {
								seller = (activeSellOrders.get(0));
								sellerQ = seller.order.getQuantity();
								if (sellerQ < buyerQuantity) {
									stockprice = (double) seller.order
											.getPriceLimit();
									buyerQuantity = buyerQuantity - sellerQ;

									if (buyer.order.getFulfilledQuantity() > 0) {
										buyer.order
												.setFulfilledQuantity(buyer.order
														.getFulfilledQuantity()
														+ sellerQ);
									} else {
										buyer.order
												.setFulfilledQuantity(sellerQ);
									}

									if (seller.order.getFulfilledQuantity() > 0) {
										seller.order
												.setFulfilledQuantity(seller.order
														.getFulfilledQuantity()
														+ sellerQ);
									} else {
										seller.order
												.setFulfilledQuantity(sellerQ);
									}

									notifyAllObservers(sellerQ);
									seller.order.closeOrder(true);
									if (seller.order.getOrderType().equals(
											OrderType.MARKET)) {
										activeMarketOrders.remove(seller);
									} else {
										activeSellOrders.remove(seller);
									}
								} else if (sellerQ > buyerQuantity) {
									int partialQuantity = sellerQ
											- buyerQuantity;
									stockprice = (double) seller.order
											.getPriceLimit();

									if (buyer.order.getFulfilledQuantity() > 0) {
										buyer.order
												.setFulfilledQuantity(buyer.order
														.getFulfilledQuantity()
														+ buyerQuantity);
									} else {
										buyer.order
												.setFulfilledQuantity(buyerQuantity);
									}

									if (seller.order.getFulfilledQuantity() > 0) {
										seller.order
												.setFulfilledQuantity(seller.order
														.getFulfilledQuantity()
														+ buyerQuantity);
									} else {
										seller.order
												.setFulfilledQuantity(buyerQuantity);
									}
									notifyAllObservers(buyerQuantity);
									newOrder(seller.order, partialQuantity);
									if (buyer.order.getOrderType().equals(
											OrderType.MARKET)) {
										activeMarketOrders.remove(buyer);
									} else {
										activeBuyOrders.remove(buyer);
									}

									if (seller.order.getOrderType().equals(
											OrderType.MARKET)) {
										activeMarketOrders.remove(seller);
									} else {
										activeSellOrders.remove(seller);
									}
									break;
								} else if (sellerQ == buyerQuantity) {
									stockprice = (double) seller.order
											.getPriceLimit();

									if (buyer.order.getFulfilledQuantity() > 0) {
										buyer.order
												.setFulfilledQuantity(buyer.order
														.getFulfilledQuantity()
														+ sellerQ);
									} else {
										buyer.order
												.setFulfilledQuantity(sellerQ);
									}

									if (seller.order.getFulfilledQuantity() > 0) {
										seller.order
												.setFulfilledQuantity(seller.order
														.getFulfilledQuantity()
														+ sellerQ);
									} else {
										seller.order
												.setFulfilledQuantity(sellerQ);
									}

									notifyAllObservers(sellerQ);
									if (buyer.order.getOrderType().equals(
											OrderType.MARKET)) {
										activeMarketOrders.remove(buyer);
									} else {
										activeBuyOrders.remove(buyer);
									}

									if (seller.order.getOrderType().equals(
											OrderType.MARKET)) {
										activeMarketOrders.remove(seller);
									} else {
										activeSellOrders.remove(seller);
									}
									break;
								}
							}

							Stock stock = StockRepository.getStockRepository()
									.getStock(
											((StockOrder) buyer.order)
													.getStockId());
							if (stockprice != null) {
								stock.setStockPrice(stockprice);
							}

						} else if (buyer.order.getOrderType().equals(
								OrderType.LIMIT)) {

							Stock stock = StockRepository.getStockRepository()
									.getStock(
											((StockOrder) seller.order)
													.getStockId());
							stock.setStockPrice(seller.order.getPriceLimit());
							buyer.order.closeOrder(true);
							if (buyer.order.getOrderType().equals(
									OrderType.MARKET)) {
								activeMarketOrders.remove(buyer);
							} else {
								activeBuyOrders.remove(buyer);
							}

							newOrder(buyer.order, buyerQuantity);
						}

						System.out
								.println("COMPLETE BUY FROM MULTIPLE SELLERS!");
						notificationformat = new EmailFormat();
						notificationformat.display();
						notification = new FullfiledOrder(notificationformat);
						System.out.println("Buyer: ");
						notification.orderFullfiledNotification();
						System.out.println("Seller: ");
						notification.orderFullfiledNotification();
					}
				}

				if (buyer != null || seller != null) {
					this.delObservers(accountBuyer);
					this.delObservers(accountSeller);
					this.delObservers(orderBuyer);
					this.delObservers(orderSeller);
					this.delObservers(buyer);
					this.delObservers(seller);
				}
			}
		}
	}

	public boolean newOrder(AbstractOrder partialOrder, int remainQuantity) {
		ActiveOrderRepository activeOrderRepository = ActiveOrderRepository
				.getOrderRepository();
		Random random = new Random();
		QueueRepository queueRepository = QueueRepository.getOrderRepository();

		ActiveOrder partialActiveOrder = new ActiveOrder();
		partialOrder.setQuantity(remainQuantity);
		partialActiveOrder.activeOrderId = partialOrder.getOrderId()
				+ (random.nextInt(1000) + 1);
		AbstractOrder newOrder = new StockOrder();
		newOrder = newOrder.newStockOrder(partialOrder);
		partialActiveOrder.order = newOrder;
		partialActiveOrder.order.initializeOrder();
		partialActiveOrder.order.activeOrder();

		activeOrderRepository.addActiveOrder(partialActiveOrder);

		if (partialActiveOrder.order instanceof StockOrder) {
			int stockId = ((StockOrder) partialActiveOrder.order).getStockId();
			Queue queue = queueRepository.getQueue(stockId);
			if (((StockOrder) partialActiveOrder.order).getOrderType().equals(
					OrderType.LIMIT)) {
				queue.add(partialActiveOrder);
				return true;
			} else if (((StockOrder) partialActiveOrder.order).getOrderType()
					.equals(OrderType.MARKET)) {
				queue.addInMarketQueue(partialActiveOrder);
				return true;
			} else {
				return false;
			}
		}
		return false;
	}

	public boolean notifyAllObservers(int matchedQuantity) {
		notifyObservers(buyer, seller, matchedQuantity);
		return true;

	}
}